//---------------------------------------------------------------------
// Copyright (C) 2010 - Cobalt Solution / Sebastien Leon              |
//---------------------------------------------------------------------
// This software is under dual licence: commercial licence + GPL v3   |
// To keep closed your modification of this software, you need to     |
// contact me to obtain a commercial licence.                         |
// Any other derivated work should remain Open Source.                |
// Contact me for a licence, a question or if you want to commit      |
// your code to the main tree: seb at cobaltsolutions dot info        |
//---------------------------------------------------------------------

#include "core.h"
#include <QDir>
#include <QPainter>
#include <QDateTime>
#include <QPrinter>
#include <QTimer>
#include <QHttp>
#include <QSvgGenerator>
#include <QDesktopServices>

#include "FTPThread.h"
#include "qttelnet.h"

// DEBUG:
//#define LICENCE_FIRST_CHECK 5000
//#define REPORTING_DELAY 6000

#define LICENCE_FIRST_CHECK   (    2*60*1000) // check after 2 minutes
#define LICENCE_VALID_CHECK   (24*60*60*1000) // Next day
#define LICENCE_INVALID_CHECK ( 1*60*60*1000) // Next hour...
#define LICENCE_HOST    "foo.net"
#define LICENCE_URL     "/cgi-bin/isLicenceValid?"
#define LICENCE_CLIENT  "testClient"

#define REPORTING_DELAY (10*60*1000) // every 10 minutes...
#define REPORTING_HOST  "ftp.foo.net"
#define REPORTING_LOGIN "loomfeed-testClient"
#define REPORTING_PASS  "werfwdsfst"
#define REPORT_NAME     "report.svg"

#define TELNET_DELAY    (5*1000) // We connect to a controller every 5 seconds

#define CPAT "core/path"
#define CCOL "rendering/bkgColor"
#define CLOO "looms"
#define MONI "monitoring"
#define REPO "reporting"

// Pref path should be:
// %COMMON_APPDATA%\CobaltSolutions.info\Loomfeed.ini  (windows)
// (%COMMON_APPDATA% usually == C:\Documents and Settings\All Users\Application Data)

static Core* sp_core = NULL;
Core* Core::Get() { return sp_core; }
//----------------------------------------------------
Core::Core():
  m_settings(QSettings::IniFormat, QSettings::SystemScope, "CobaltSolutions.info", "Loomfeed"),
  m_licenceBuffer  (&m_licenceReq),
  m_reportingBuffer(&m_reportingImg),
  m_resourceID     (0),
  m_reportingFTP   (this),
  m_licenceValid   (true)
{
  // Init Static instance ref:
  Q_ASSERT(sp_core == NULL);
  sp_core = this;

  // Initialize structures:
  // Scanned Path:
  m_path = m_settings.value(CPAT).toString();
  if (m_path.isEmpty()) m_path = QDesktopServices::storageLocation (QDesktopServices::DesktopLocation);

  // Load Background color & Refresh the graphic representation of the looms
  m_bkgColor = m_settings.value(CCOL, Qt::yellow).toString();
  m_loomScene.setBackgroundBrush(m_bkgColor);
  m_loomScene.addItem(new QGraphicsLineItem(0,0,1,0));

  // list of Looms:
  int nbLoom = m_settings.beginReadArray(CLOO); // Bug Qt ? (returned value is always 0 :-( )
  for (int i = 0; i < nbLoom; ++i) 
  {
    m_settings.setArrayIndex(i);
    Loom* newLoom = new Loom;
    newLoom->LoadLoomFromSettings(m_settings);
    m_loomList.append(newLoom);
    // Add to the loom to the scene
    m_loomScene.addItem(newLoom);
  }
  m_settings.endArray();

  m_monitoringCore = m_settings.value(MONI, true).toBool();
  m_reportingCore  = m_settings.value(REPO, true).toBool();
  connect(&m_reportingFTP, SIGNAL(done(bool)), SLOT(done(bool)));

  // m_licenceValid will be checked in 2 minutes
  QTimer::singleShot(LICENCE_FIRST_CHECK, this, SLOT(CheckLicence()));

  // Periodic reporting:
  QTimer::singleShot(REPORTING_DELAY, this, SLOT(ReportingReq()));

  // First Telnet Thread
  QTimer::singleShot(500, this, SLOT(StartTelnetConnexions()));
}
//----------------------------------------------------
void Core::done(bool error)
{
  qDebug() << (error?QString("FTP error : %1").arg(m_reportingFTP.errorString()):QString("FTP OK"));
  if (error) m_reportingFTP.close();
}
//----------------------------------------------------
void Core::ReportingReq ()
{
  if (IsReporting() && m_reportingFTP.state() == QFtp::Unconnected)
  {
    // Prepare Report
    m_reportingBuffer.close(); // if already opened...
    {
      QString report = CreateReport(e_Svg);
      QFile fileIn(report);
      fileIn.open(QIODevice::ReadOnly);
      m_reportingImg = fileIn.readAll();
      fileIn.close();
    }
    m_reportingBuffer.open(QIODevice::ReadOnly);

    // Send it to host
    m_reportingFTP.connectToHost(REPORTING_HOST);
    m_reportingFTP.login(REPORTING_LOGIN, REPORTING_PASS);                          
    m_reportingFTP.put  (&m_reportingBuffer, REPORT_NAME);                  
    m_reportingFTP.close(); 
  }
  // Periodic reporting:
  QTimer::singleShot(REPORTING_DELAY, this, SLOT(ReportingReq()));
}
//----------------------------------------------------
void Core::SaveSettings()
{
  // Scanned Path:
  m_settings.setValue(CPAT, m_path);

  // Background color:
  m_settings.setValue(CCOL, m_bkgColor.name());

  // list of Looms:
  m_settings.beginWriteArray(CLOO);
  for (int i = 0; i < m_loomList.count(); ++i) 
  {
    m_settings.setArrayIndex(i);
    m_loomList.at(i)->SaveLoomInSettings(m_settings);
  }
  m_settings.endArray();
}
//----------------------------------------------------
Core::~Core()
{
  // Free all Looms objects
  while (!m_loomList.isEmpty()) { delete m_loomList.takeLast (); }
}
//----------------------------------------------------
bool Core::ValidLoomGraphicChange (const Loom* p_loom)
{
  int  id  = m_loomList.indexOf(const_cast<Loom*>(p_loom));
  bool res = (id >= 0);
  if (res)
  {
    m_loomList[id]->AlignGrid();
    // Inform the world:
    emit LoomChanged(*m_loomList[id], e_graphicChanged);
    // Update Inifile
    SaveSettings();
  }
  return res;
}
//----------------------------------------------------
const Loom* Core::FindLoomByIP(const QHostAddress& address)
{
  const Loom* res = NULL;
  for (int n = 0 ; res && n < m_loomList.count() ; ++n)
  {
    if (m_loomList[n]->Address() == address)
    {
      res = m_loomList[n];
      break;
    }
  }
  return res;
}
//----------------------------------------------------
bool Core::AddLoom (Loom* newLoom)
{
  if (!newLoom) return false;
  // newLoom will be added to the list... The Core class will manage it. Do not destroy it... 
  // return false if this loom is already in the list or if another loom has the same address or name
  bool res = true;
  for (int n = 0 ; res && n < m_loomList.count() ; ++n)
  {
    res = (newLoom!=m_loomList[n]) && !m_loomList[n]->IdenticalNameOrAddress(*newLoom);
  }
  if (res)
  {
    // Add to the loom list
    m_loomList.append(newLoom);
    // Add to the loom to the scene
    m_loomScene.addItem(newLoom);
    // Inform the world:
    emit LoomChanged(*newLoom, e_loomAdded);
    // Update Inifile
    SaveSettings();
    // Connect this Loom object to the real Loom:
    StartTelnetConnexions();
  }
  return res;
}
//----------------------------------------------------
bool Core::DeleteLoom (const Loom* p_loom)
{
  if (!p_loom) return false;
  int  id  = m_loomList.indexOf(const_cast<Loom*>(p_loom));
  bool res = (id >= 0);
  if (res)
  {
    // Inform the world:
    emit LoomChanged(*m_loomList[id], e_loomWillBeDeleted);
    // Remove from the loom list (automatic remove from scene)
    delete m_loomList.takeAt(id);
    // Update Inifile
    SaveSettings();
  }
  return res;
}
//----------------------------------------------------
bool Core::UpdateLoom (const Loom* p_oldLoom, const Loom& newLoom)
{
  if (!p_oldLoom) return false;
  // Update oldLoom with a new one (return false if oldLoom is not found 
  // or if another loom has already this name or address)
  int  id  = m_loomList.indexOf(const_cast<Loom*>(p_oldLoom));
  bool res = (id >= 0);
  for (int n = 0 ; res && n < m_loomList.count() ; ++n)
  {
    if (n!=id) res = !m_loomList[n]->IdenticalNameOrAddress(newLoom);
  }
  if (res)
  {
    *m_loomList[id] = newLoom;
    // Inform the world:
    emit LoomChanged(*m_loomList[id], e_loomChanged); // To do: send a more fine EModifType
    // Update Inifile
    SaveSettings();
  }
  return res;
}
//----------------------------------------------------
QString Core::Path (const QHostAddress& address) const
{
  QString res(m_path); // Base path
  // Find the correct Loom and combine its path and Core::path
  for (int n = 0 ; n < m_loomList.count() ; ++n)
  {
    if (address == m_loomList[n]->Address())
    {
      res += '/'+m_loomList[n]->SrcPath();
      break;
    }
  }
  return res;
}
//----------------------------------------------------
bool Core::ChangeBkgColor (const QColor& col )
{
  m_bkgColor = col;
  // Refresh the graphic representation of the looms
  m_loomScene.setBackgroundBrush(m_bkgColor);
  // Inform the world:
  emit BkgColorModified();
  // Update Inifile
  SaveSettings();
  return true;
}
//----------------------------------------------------
bool Core::ChangePath (const QString& path)
{
  bool res = QDir(path).exists();
  if (res)
  {
    m_path = path;
    // Inform the world:
    emit PathModified();
    // Update Inifile
    SaveSettings();
  }
  return res;
}
//----------------------------------------------------
void Core::InitiateTransfer(QFileInfo& nfo, const Loom* p_loomConst)
{
  // First, we check the source loom (NULL means all looms!)
  Loom* p_loom = NULL;
  if (p_loomConst)
  {
    int id = m_loomList.indexOf(const_cast<Loom*>(p_loomConst));
    if (id>=0) p_loom = m_loomList[id];
  }
  // if the loom has not been recognized, we brodcat to all looms

  // Second : We check if the resource is usable
  if (nfo.exists() && nfo.isFile() && nfo.isReadable())
  {
    bool locking = true;
    // First : We lock the ressource
    // We copy it to TMP dir
    ++m_resourceID;
    QDir tmpPath (QDir::tempPath() + QDir::separator() + "Loomfeed" );
    if (!tmpPath.exists()) locking &= QDir(QDir::tempPath()).mkdir("Loomfeed");
    if (!locking)
    {
      emit Message("Erreur a la creation d'un repertoire temporaire pour verrouiller les ressources... Operation annulee", QSystemTrayIcon::Warning);
    }
    else
    {
      QString resName( QString("res_%1").arg(m_resourceID) );
      QString resPath(tmpPath.absolutePath() + QDir::separator() + resName );
      // Clean old resource:
      if (QFile(resPath).exists()) tmpPath.remove(resName);
      locking &= QFile::copy ( nfo.filePath(), resPath );
      if (!locking)
      {
        emit Message(QString("Erreur a la copie du fichier %1. Transmission de ce fichier abandonnee...").arg(nfo.fileName()), QSystemTrayIcon::Warning);
      }
      else
      {
        // Add it to the resource list
        m_resourceList.append(Resource(m_resourceID, nfo.fileName(), resPath));

        // Create the threads to upload the file...
        if (p_loom && p_loom->IsEnabled())
        {
          FTPThread* p_thread = new FTPThread(*this, *p_loom, m_resourceID);
          p_loom->AddThread (p_thread);
        }
        else
        {
          // We broadcast the file to all looms !
          for (int l = 0 ; l < m_loomList.count() ; ++l)
          {
            Loom* p_l = m_loomList[l]; Q_ASSERT(p_l);
            if (p_l->IsEnabled())
            {
              FTPThread* p_thread = new FTPThread(*this, *p_l, m_resourceID);
              p_l->AddThread (p_thread);
            }
          }          
        }
      }
    }
  }
}
//----------------------------------------------------
Core::Resource* Core::GetResourceFromID (int id)
{
  Resource* res = NULL;
  for (int n = 0 ; n<m_resourceList.count() && !res ; ++n)
  {
    if (m_resourceList.at(n).m_id == id) res = &m_resourceList[n];
  }
  return res;
}
//----------------------------------------------------
QString Core::GetResourceName (int id) 
{
  const Resource* res = GetResourceFromID(id);
  Q_ASSERT(res);
  return res ? res->m_name : QString::null;
}
//----------------------------------------------------
QString Core::GetAndLockResource(int id) 
{
  Resource* res = GetResourceFromID(id);
  Q_ASSERT(res);
  if (res) res->m_count++; 
  return res ? res->m_path : QString::null;
}
//----------------------------------------------------
void Core::UnlockResource (FTPThread& th)
{
  Resource* res = GetResourceFromID(th.GetResID());
  Q_ASSERT(res);
  // Free resource if counter is 0
  if (res)
  {
    emit Message( th.TransferOk() ?
      QString("Fichier %1 transmis au metier \"%2\"").arg(res->m_name).arg(th.GetLoom().Name()) :
      QString("Erreur de transfert pour le fichier %1. Ouvrez le log du metier \"%2\"").arg(res->m_name).arg(th.GetLoom().Name()),
      (th.TransferOk() ? QSystemTrayIcon::Information : QSystemTrayIcon::Warning)
    );

    // done directly by signal emited by thread.
    //th.GetLoom().SetTransferSucceded(th.TransferOk());

    res->m_count--;
    if (res->m_count < 1)
    {
      // Freeing the resource
      qDebug() << QString("Free the resource %1").arg(res->m_name);

      QFile fileto(res->m_path);
      fileto.remove();
    }
  }
  // We remove this Thread from the Loom Thread list...
  qDebug() << QString("Remove this thread (uploading %1) from Loom list...").arg(res->m_name);
  th.GetLoom().NextThread(&th);
}
//----------------------------------------------------
void Core::StartTelnetConnexions()
{
  // We parse the list of looms and start a Telnet thread for each of us
  // One at a time (every second...)
  int n;
  for (n = 0 ; n<m_loomList.count() ; ++n)
  {
    if (!m_loomList[n]->HasTelnetCom())
    {
      m_loomList[n]->RestartTelnetCom();
      break;
    }
  }
  // Check m_loomList to know if it is useful to call again this function ?
  if (n < m_loomList.count()) QTimer::singleShot(TELNET_DELAY, this, SLOT(StartTelnetConnexions()));
}
//----------------------------------------------------
void Core::CheckLicence()
{
  // Check it... Prepare an HTTP request:
  m_licenceReq.clear();
  m_licenceBuffer.open(QIODevice::WriteOnly);
  QHttp* request = new QHttp(LICENCE_HOST);
  request->get(QString(LICENCE_URL)+LICENCE_CLIENT, &m_licenceBuffer);
  connect(request, SIGNAL(requestFinished(int,bool)), SLOT(requestFinished(int,bool)));
}
//----------------------------------------------------
void Core::requestFinished ( int id, bool error )
{
  Q_UNUSED(id);
  m_licenceBuffer.close();
  if (error)
  {
    qDebug() << ((QHttp*)sender())->errorString();
    m_licenceValid = false;
    emit Message("Impossible de verifier la validite de la maintenance logicielle.\nLe monitoring/reporting est temporairement desactive.\nSi ce probleme est inattendu, contactez CobaltSolution.info...", QSystemTrayIcon::Warning);
  }
  else
  {
    qDebug() << m_licenceReq;
    m_licenceValid = m_licenceReq[0]=='1';
    QString info(m_licenceReq);
    info = info.right(info.length() - 2);
    if (!info.isEmpty()) emit Message(info, (m_licenceValid ? QSystemTrayIcon::Information : QSystemTrayIcon::Warning) );
  }
  QObject* req = sender();
  if (req) req->deleteLater();
  // Check soon if invalid or next day if valid...
  QTimer::singleShot((m_licenceValid?LICENCE_VALID_CHECK:LICENCE_INVALID_CHECK), this, SLOT(CheckLicence()));
}
//----------------------------------------------------
QString Core::CreateReport(EReportType type)
{
  QString path;
  if (m_loomList.count() > 0)
  {
    path = QDir::tempPath() + QDir::separator() + "report." + QString((type == e_Svg)?"svg":"pdf");
    QRectF r(m_loomScene.itemsBoundingRect()); r.adjust(0,0,50,50);
    QString title(QString("RAPPORT LoomFeed (%1)").arg(QDateTime::currentDateTime().toString()));
    QPainter p;

    if (type == e_Svg)
    {
      QSvgGenerator sgvO;
      sgvO.setFileName(path);
      sgvO.setSize(QSize(r.size().width(), r.size().height()));
      sgvO.setViewBox(r);
      sgvO.setResolution(96);
      sgvO.setTitle(title);
      if (p.begin(&sgvO))
      {
        m_loomScene.render(&p, r, r);
        p.end();
      } else path = QString::null;
    }
    else
    {
      QPrinter pdfO;
      pdfO.setOutputFileName (path);
      pdfO.setOutputFormat   (QPrinter::PdfFormat);
      pdfO.setDocName        (title);
      pdfO.setCreator        ("LoomFeed by CobaltSolutions.info");
      //pdfO.setPaperSize      (QSize(r.width()/96, r.height()/96), QPrinter::Inch);
      pdfO.setResolution     (72);
      pdfO.setOrientation    (QPrinter::Landscape);
      if (p.begin(&pdfO))
      {
        // Draw all items:
        //QStyleOptionGraphicsItem opt;
        for (int l = 0 ; l < m_loomList.count() ; ++l)
        {
          Loom* p_l = m_loomList[l];
          p.resetTransform();
          p.translate(p_l->Pos().x(), p_l->Pos().y());
          p_l->paint(&p, NULL, NULL);
        }
        p.resetTransform();
        p.drawText(0,0, title);
        p.end();
      } else path = QString::null;
    }
  }
  return path;
}
//----------------------------------------------------
//----------------------------------------------------
// LOOM OBJECT
#define ENAB  "On"   
#define MOTR  "Monitor"   
#define NAME  "Name"   
#define ADDR  "Address"
#define INF1  "Info1"   
#define INF2  "Info2"   
#define POSX  "PosX"   
#define POSY  "PosY"   
#define PATH  "Path"
#define COLO  "Color"
#define WIDT  "Width"
#define HEIG  "Height"

#define NB_TRY  4 // 4x30 sec = 2 minutes

#define LOOM_CMD "weave status"

#define CAPTION_HEIGHT   16
#define ITEM_MIN_WIDTH  (qreal) 80
#define ITEM_MAX_WIDTH  (qreal)300
#define ITEM_MIN_HEIGHT (qreal) 5*CAPTION_HEIGHT
#define ITEM_MAX_HEIGHT (qreal)300
#define GRID_X          10
#define GRID_Y          10


QPixmap* Loom::msp_resize = NULL;
void Loom::InitResize(){ if (!msp_resize) msp_resize = new QPixmap(":/resize.png");}

//-- CONSTRUCTOR -------------------------------------
Loom::Loom (const Loom& l): QGraphicsObject(NULL), // Create a copy of the passed loom
  m_enabled   (l.m_enabled   ),
  m_monitoring(l.m_monitoring),
  m_name      (l.m_name      ),
  m_address   (l.m_address   ),
  m_info1     (l.m_info1     ),
  m_info2     (l.m_info2     ),
  m_boxColor  (l.m_boxColor  ),
  m_size      (l.m_size      ),
  m_path      (l.m_path      ),
  m_log       (l.m_log       ),

  m_ftpName   (l.m_ftpName   ),
  m_ftpProg   (l.m_ftpProg   ),
  m_ftpState  (l.m_ftpState  ),

  mp_telnet   (         NULL  ), // No telnet from it
  m_state     ( e_connecting  ),
  m_info      (QString::null  )
{
  InitResize();
  setPos(l.pos());
}
//-- CONSTRUCTOR -------------------------------------
Loom::Loom (): QGraphicsObject(NULL), // Create an invalid Loom (must be filled or read from ini file...
  m_enabled   (true         ),
  m_monitoring(true         ),
  m_name      ("METIER ?"   ),
  m_address   (QString::null),
  m_info1     (QString::null),
  m_info2     (QString::null),
  m_boxColor  (Qt::yellow   ),
  m_size      (40,35        ),
  m_path      (QString::null),

  m_ftpName   (QString::null),
  m_ftpProg   (          0.0),
  m_ftpState  (      e_noFTP),

  mp_telnet   (         NULL),// No telnet by now
  m_state     ( e_connecting),
  m_info      (QString::null)
{
  InitResize();
  setPos(QPointF(10,10));
}
//----------------------------------------------------
Loom::~Loom ()
{
  ReleaseTelnet();
}
//----------------------------------------------------
void Loom::ReleaseTelnet()
{
  if (mp_telnet)
  {
    killTimer(m_timerID);
    mp_telnet->deleteLater();
    mp_telnet = NULL;
  }
}
//----------------------------------------------------
void Loom::RestartTelnetCom ()
{
  ReleaseTelnet(); // if any

  qDebug() << "Start new TELNET connexion on LOOM " << m_name;
  mp_telnet = new QtTelnet(this);
  mp_telnet->setPromptString ("# ");
  connect(mp_telnet, SIGNAL(connectionError (QAbstractSocket::SocketError)), SLOT(connectionError (QAbstractSocket::SocketError) ));
  connect(mp_telnet, SIGNAL(loggedIn        ()                            ), SLOT(loggedIn        ()                             ));
  connect(mp_telnet, SIGNAL(loginFailed     ()                            ), SLOT(loginFailed     ()                             ));
  connect(mp_telnet, SIGNAL(loginRequired   ()                            ), SLOT(loginRequired   ()                             ));
  connect(mp_telnet, SIGNAL(message         (const QString&)              ), SLOT(message         (const QString&)               ));
  // Let start it:
  m_timerID = startTimer(30 * 1000); // every half minute
  m_state   = e_undefined;
  // Now:
  timerEvent(NULL);
}
//----------------------------------------------------
Loom& Loom::operator = (const Loom& l) // copy from the passed loom
{
  m_enabled   = l.m_enabled   ;
  m_monitoring= l.m_monitoring;
  m_name      = l.m_name      ;
  m_info1     = l.m_info1     ;
  m_info2     = l.m_info2     ;
  m_boxColor  = l.m_boxColor  ;
  setPos(     l.pos())        ;
  m_size      = l.m_size      ;
  m_path      = l.m_path      ;
  m_log       = l.m_log       ;

  m_ftpName   = l.m_ftpName   ;
  m_ftpProg   = l.m_ftpProg   ;
  m_ftpState  = l.m_ftpState  ;

  // Thread is not transfered but if the IP address changed, we have to restart it (if any)
  if (m_address != l.m_address)
  {
    m_address = l.m_address ;
    if (mp_telnet) RestartTelnetCom();
  }
  update(); // Update GUI (GraphicsView if any...)
  return *this;
}
//----------------------------------------------------
bool Loom::IdenticalNameOrAddress (const Loom& l) const
{
  return (l.Name() == Name()) || (l.Address() == Address());
}
//----------------------------------------------------
void Loom::TransferInProgress  (const int s, const QString& name, const float progress) // SLOT
{
  m_ftpProg = progress;
  m_ftpState= (EFTPState)s;
  m_ftpName = (m_ftpState == e_okFTP) ? m_ftpName : name; // we keep the same name...
  if (progress == 1.0) m_ftpName = QString::null; // Done...

  update(); // will execute in another thread
}
//----------------------------------------------------
void Loom::LoadLoomFromSettings (const QSettings& settings)
{
  QPointF pos;
  m_enabled   =    settings.value(ENAB, true).toBool  ();   
  m_monitoring=    settings.value(MOTR, true).toBool  ();   
  m_name      =    settings.value(NAME).toString();   
  m_info1     =    settings.value(INF1).toString();
  m_info2     =    settings.value(INF2).toString();
  m_path      =    settings.value(PATH).toString();
  pos.setX(        settings.value(POSX).toReal  ());
  pos.setY(        settings.value(POSY).toReal  ());
  m_size.setWidth (settings.value(WIDT,100).toReal  ());
  m_size.setHeight(settings.value(HEIG, 80).toReal  ());
  m_address.setAddress(settings.value(ADDR).toString());
  m_boxColor.setNamedColor (settings.value(COLO).toString());
  setPos(pos);
}
//----------------------------------------------------
void Loom::SaveLoomInSettings (QSettings& settings) const
{
  settings.setValue(ENAB, m_enabled   );
  settings.setValue(MOTR, m_monitoring);
  settings.setValue(NAME, m_name      );
  settings.setValue(INF1, m_info1     );
  settings.setValue(INF2, m_info2     );
  settings.setValue(PATH, m_path      );
  settings.setValue(POSX, pos().x()   );
  settings.setValue(POSY, pos().y()   );
  settings.setValue(ADDR, m_address.toString());
  settings.setValue(WIDT, m_size.width      ());
  settings.setValue(HEIG, m_size.height     ());
  settings.setValue(COLO, m_boxColor.name   ());
}
//----------------------------------------------------
QRectF Loom::boundingRect() const
{
  // qreal penWidth = 1;
  return QRectF(QPointF(), m_size);
}
//----------------------------------------------------
bool Loom::IsInCaption (const QPointF& p) const
{
  return QRectF(pos(), QSizeF(m_size.width(),CAPTION_HEIGHT)).contains(p);
}
//----------------------------------------------------
bool Loom::IsInBody (const QPointF& p) const
{
  return QRectF(QPointF(pos().x(),pos().y()+CAPTION_HEIGHT),QSizeF(m_size.width(),m_size.height()-CAPTION_HEIGHT)).contains(p) 
    && !IsInResize(p);
}
//----------------------------------------------------
bool Loom::IsInResize (const QPointF& p) const
{
  return QRectF(QPointF(pos().x()+m_size.width()-msp_resize->width(),pos().y()+m_size.height()-msp_resize->height()), msp_resize->size()).contains(p);
}
//----------------------------------------------------
void Loom::AlignGrid ()
{
  SetPos (QPointF(((int)Pos().x()      + GRID_X/2)/GRID_X*GRID_X, ((int)Pos().y()       + GRID_Y/2)/GRID_Y*GRID_Y));
  SetSize(QSizeF (((int)Size().width() + GRID_X/2)/GRID_X*GRID_X, ((int)Size().height() + GRID_Y/2)/GRID_Y*GRID_Y));
}
//----------------------------------------------------
void Loom::SetSize (const QSizeF& size)
{
  QSizeF sz(max(ITEM_MIN_WIDTH,min(ITEM_MAX_WIDTH, size.width())), max(ITEM_MIN_HEIGHT,min(ITEM_MAX_HEIGHT, size.height())));
  m_size = sz; 
  prepareGeometryChange(); 
  update(); 
}; 
//----------------------------------------------------
void Loom::SetPos (const QPointF& pos)
{
  QPointF pt(max((qreal)0,pos.x()), max((qreal)0,pos.y()));
  setPos(pt);  // refresh is already done by setPos()
}
//----------------------------------------------------
void Loom::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
  Q_UNUSED(widget);
  Q_UNUSED(option);

  bool onPDF = dynamic_cast<QPrinter*     >(painter->device());
  bool onSVG = dynamic_cast<QSvgGenerator*>(painter->device());
  bool onReport = onPDF || onSVG;

  const QColor A_Enabled  ("#F0F0E0");
  const QColor A_Disabled ("#808080");
  const QColor B_Neutral  ("#93D5FF"); // light blue...
  const QColor B_Error    ("#FF2323"); // lightRed
  const QColor B_Connect  ("#FFFF00"); // yellow
  const QColor B_Transfer ("#26B200"); // Green
  const QColor B_Progress ("#4CFF00"); // lightGreen
  // We will paint in 3 parts:
  //   ______________________
  //   | CAPTION (lightGray)|   A = Moveable area
  //   |____________________|
  //   | FEED-BACK ABOUT    |   B
  //   |--------------------|
  //   | STATUS (2 lines)   |   B
  //   |____________________|
  //   |                    |
  //   | BODY (2 INFOS) +   |   C
  //   | USER-DEFINED COLOR |
  //   |                    |
  //   |                 ___|
  //   |                | R |  = Resize area
  //   |________________|___|
  //

  //------------------------------------------------
  // we print the name: BOX A
  qreal w = CAPTION_HEIGHT;
  painter->setPen   (QPen(Qt::black,1));
  QRectF txtR(QPointF(), m_size); txtR.setHeight(w);
  QFont F("Arial", 9);
  painter->setBrush (m_enabled ? A_Enabled:A_Disabled); // White or Dark Gray
  painter->drawRect (QRectF(QPointF(), m_size));
  if (!onPDF) F.setBold(true);
  painter->setFont  (F);
  painter->drawText (txtR, Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine, " "+m_name);
  F.setBold(false);
  // Now we paint the feed back about the last Download:
  F = QFont ("Arial", 8);
  painter->setFont  (F);
  if (m_ftpName.isEmpty() && m_ftpState != e_noFTP)
  {
    QRectF downlR(QPointF(txtR.right()-CAPTION_HEIGHT+2, txtR.top()+2), QSizeF(CAPTION_HEIGHT-4,CAPTION_HEIGHT-4));
    painter->setBrush ((m_ftpState==e_connectFTP) ? B_Connect : ((m_ftpState==e_errorFTP) ? Qt::red : B_Transfer )); // bkg
    painter->drawEllipse (downlR);
    painter->drawText    (downlR, Qt::AlignCenter, "T");
  }
  //------------------------------------------------
  // we print the feed-back (& progress bar): BOX B, 2 lines
  txtR.moveTop(w);
  QRectF boxB(txtR.topLeft(), QSize(txtR.width(), 2*w));
  if (IsEnabled())
  {
    if (!onReport && !m_ftpName.isEmpty())
    {
      // Show Transfert Infos
      painter->setBrush ((m_ftpState==e_connectFTP) ? B_Connect : ((m_ftpState==e_errorFTP) ? B_Error : B_Transfer ));
      painter->drawRect (boxB);
      painter->drawText (txtR, Qt::AlignVCenter | Qt::AlignLeft, " Transfert:" );
      txtR.moveTop(2*w); // Second line
      if (m_ftpState==e_okFTP)
      {
        painter->setBrush (B_Progress);
        painter->setPen   (QPen(Qt::green,1));
        painter->drawRect (QRectF(txtR.x()+2, txtR.y()+2, m_ftpProg*(txtR.width()-4), txtR.height()-4));
        painter->setPen   (QPen(Qt::black,1));
        painter->drawText (txtR.adjusted (3,3,-3,-3), Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine, QString("%1%: %2").arg((int)(m_ftpProg*100+0.5)).arg(m_ftpName) );
      }
      else
      {
        painter->drawText (txtR, Qt::AlignCenter, "...connexion..." );
      }
    }
    else
    {
      if (IsMonitoring())
      {
        QRectF txtR2(txtR); txtR2.moveTop(2*w);
        painter->setBrush ((GetState()==e_connecting || GetState()==e_not_a_JC) ? B_Connect : 
                           (GetState()==e_error                               ) ? B_Error   :
                           (GetState()==e_undefined                           ) ? B_Neutral :
                           (GetState()==e_jcFound                             ) ? B_Neutral :
                           (GetState()==e_locked                              ) ? B_Connect :
                           (GetState()==e_stopped_backward                    ) ? B_Error   :
                           (GetState()==e_stopped_forward                     ) ? B_Error   :
                                                                                  B_Transfer );
        painter->drawRect (boxB);
        QString msg1, msg2;
             if (GetState()==e_connecting) { msg1 = "Connexion";    msg2 = "en cours...";   }
        else if (GetState()==e_not_a_JC  ) { msg1 = "Staubli JC5";  msg2 = "non reconnu";   }
        else if (GetState()==e_error     ) { msg1 = "Erreur de";    msg2 = "communication"; }
        else if (GetState()==e_undefined ) { msg1 = "Etat";         msg2 = "inconnu";       }
        else if (GetState()==e_jcFound   ) { msg1 = "Interrogation";msg2 = "du Staubli JC5";}
        else 
        {
          msg2 = m_info;
          msg1 = // e_locked, e_stopped_backward, , , 
            (GetState()==e_locked          ) ? "VERROUILLE" :
            (GetState()==e_stopped_backward) ? "STOP ARR."  :
            (GetState()==e_stopped_forward ) ? "STOP AVANT" :
            (GetState()==e_running         ) ? "EN MARCHE"  :
            (GetState()==e_weaving         ) ? "EN TISSAGE" : "???";
        }
        painter->drawText (txtR , Qt::AlignCenter, msg1 );
        painter->drawText (txtR2, Qt::AlignCenter, msg2 );
      }
      else
      {
        painter->setBrush (B_Neutral);
        painter->drawRect (boxB);
        painter->drawText (boxB , Qt::AlignCenter, "Pas de\nMonitoring" );
      }
    }
  }
  else
  {
      painter->setBrush (A_Disabled);
      painter->drawRect (boxB);
      painter->drawText (boxB , Qt::AlignCenter, "Non actif..." );
  }
  //------------------------------------------------
  // we print the user-box: BOX C
  txtR.moveTop(3*w);
  painter->setBrush (m_boxColor);
  painter->drawRect (QRectF(txtR.topLeft(), QSize(txtR.width(), m_size.height()-3*w)));
  //painter->setFont  (F);
  painter->drawText (txtR, Qt::AlignCenter, m_info1);
  txtR.moveTop(4*w); txtR.setHeight(m_size.height()-4*w);
  painter->drawText (txtR, Qt::AlignCenter | Qt::TextWordWrap, m_info2);

  // finally, we put the resize corner:
  if (!onReport) painter->drawPixmap (QPointF(m_size.width()-msp_resize->width()-1, m_size.height()-msp_resize->height()-1), *msp_resize);
}
//----------------------------------------------------
void Loom::AddLogEntry (const QString& caption, const QString& msg, QMessageBox::Icon msgType)
{
  QString date( QDateTime::currentDateTime().toString("dd MMM yyyy, hh:mm") );
  QColor color( msgType == QMessageBox::Information ? Qt::darkGreen : 
                msgType == QMessageBox::Warning     ? Qt::darkYellow:
                msgType == QMessageBox::Critical    ? Qt::red       : Qt::black);

  // Remove oldest entries...
  if (m_log.count() == LOG_MSG_QUOTA) m_log.removeFirst();

  // Append new entry:
  QString logFormat("<font color=\"darkBlue\">%1:</font> <font color=\"%2\"><b>%3</b></font>: <font size=-1>%4</font>");
  m_log << logFormat.arg(date).arg(color.name()).arg(caption).arg(msg);
}
//----------------------------------------------------
void Loom::NextThread(FTPThread* p_thread)
{
  // remove this one:
  if (p_thread)
  {
    m_threadList.removeAll(p_thread);
    p_thread->deleteLater();
  }
  if (m_threadList.count())
  {
    qDebug() << QString("Start Queued upload Thread [loom %1]...").arg(m_name);
    m_threadList[0]->start(); // Go!
  }
}
//----------------------------------------------------
void Loom::AddThread (FTPThread* p_thread)
{
  // Add this thread to the end of the list.
  m_threadList.append(p_thread);

  // Start this thread if there is no other running...
  qDebug() << QString("Start first upload Thread [loom %1]...").arg(m_name);
  if (m_threadList.count() == 1)
  {
    p_thread->start(); // Go!
  }
}
//----------------------------------------------------
void Loom::UpdateState(ELoomState s, const QString& info)
{
  if (m_state != s && m_info != info)
  {
    m_state = s;
    m_info  = info;
    update();
  }
}
//----------------------------------------------------
void Loom::connectionError ( QAbstractSocket::SocketError error )
{
  Q_UNUSED(error);
  qDebug() << "connectionError...";
  // Report the error. The timer will make another try later...
  UpdateState(e_error);
}
//----------------------------------------------------
void Loom::loggedIn ()
{
  qDebug() << "loggedIn...";
  // Good, but what we want is the welcome msg. We ignore this event...
}
//----------------------------------------------------
void Loom::loginFailed ()
{
  UpdateState(Loom::e_not_a_JC);
  // JC do not ask for authenfication...
  qDebug() << "loginFailed : THIS IS NOT A STAUBLI JC !";
  // Close connexion for now... We will make another try later
  mp_telnet->close();
}
//----------------------------------------------------
void Loom::loginRequired ()
{
  UpdateState(Loom::e_not_a_JC);
  // JC do not ask for authenfication...
  qDebug() << "loginRequired : THIS IS NOT A STAUBLI JC !";
  // Close connexion for now... We will make another try later
  mp_telnet->close();
}
//----------------------------------------------------
void Loom::message ( const QString & data )
{
  qDebug() << "Get message from remote serveur";
  QString cleanStr(data.trimmed());
  // Good we get info
  if (cleanStr.startsWith("JC5"))
  {
    UpdateState(Loom::e_jcFound);
    // The server replied. No information but we know it is there...
    qDebug() << "Connexion JC5...";
  }
  else if (cleanStr.startsWith("Welcome"))
  {
    // The server replied again with the welcome message
    qDebug() << "Welcome message...";
    // It is time to ask about his state...
    mp_telnet->sendData (LOOM_CMD );
  }
  else if (cleanStr.startsWith(LOOM_CMD))
  {
    // The server is echoing the request... We ignore it.
    // We wait for the answer !
    qDebug() << "Server echo request...";
  }
  else
  {
    // Decode
    /* Report example:
    LOCKED 0
    SYNCHRONIZED
    program: default.prg
    exec:           1/-1 
    pick:           1/20 
    cycle:          1
    design:    TOTO.jc5
    exec:           1/1  
    pick:           1/20 
    cycle:          1/20 
    pick/cycle:     1/1
    */
    QString nfo;
    QStringList datas = cleanStr.split("\r\n");
    // Let's check the first line:
    QStringList line1 = datas[0].split(' '); // cut words
    ELoomState detectedState = line1.count() > 0 ? (
      (line1[0] == "LOCKED" ) ? e_locked :
      (line1[0] == "STOPPED") ? ((line1[1] == "BACKWARD") ? e_stopped_backward : e_stopped_forward ):
      (line1[0] == "RUNNING") ? e_running :
      (line1[0] == "WEAVING") ? e_weaving :
                                e_undefined ) : e_undefined;
    // Did we recognized something ?
    if (detectedState!=e_undefined && datas.count()>1)
    {
      // Yes ! let's try to read the rest of the message:
      if (datas[1].startsWith("SYNCHRONIZED")) nfo = "[S] ";
      // let's try to find the file used:
      QStringList design = datas[(datas[2].startsWith("design")) ? 2 : 6 ].split(':');
      if (design[0].startsWith("design")) nfo += design[1].trimmed();
      nfo.replace("NO FILE", "<aucun>");
    }
    // Report this info:
    UpdateState(detectedState, nfo);
    // Disconnect
    mp_telnet->close();
  }
}
//----------------------------------------------------
void Loom::timerEvent ( QTimerEvent * event ) // periodic call...
{
  Q_UNUSED(event);

  // The Loom can be desabled
  if (!m_enabled) return;

  // The monitoring function can be desabled
  if (!IsMonitoring()) return;

  if (GetState() == e_connecting) // Connecting is already in progress :-(
  {
    if (++m_connectTry < NB_TRY) return; else RestartTelnetCom (); 
  }
  else
  {
    UpdateState(Loom::e_connecting); // report the tentative

    mp_telnet->connectToHost (Address().toString()); // Go !!!
    m_connectTry = 0; // reset timeout...
  }
}
//----------------------------------------------------